1 Usa pacman para cargar las librerías e instalarlas si no están disponibles

Todos los ejercicios planteados están relacionados con el uso de las librerías dplyr, tidyr y lubridate. Familiarízate con el uso del operador %>% para facilitar la compresión del código. Los ejercicios consideran que las librería están instaladas y cargadas.

2 Fichero de datos.

Todas las preguntas están referidas al fichero 2024-03-12.Rdata, que contiene información de los registros (logs) de las descargas realizadas del repositorio CRAN en esa fecha. La información contenida en el fichero es la siguiente:

El material está basado en el curso ‘Getting and Cleaning data’ de la librería swirlr

Se pueden descargar ficheros actuales del logs usando la librería https://r-hub.github.io/cranlogs/

  1. Carga el fichero de datos y almacénalo en un data frame llamado mydf. Asegúrate que la opción stringsAsFactors=FALSE. Mira las dimensiones y los primeros datos.

Nota: El siguiente bloque puede tardar en ejecutarse por lo que solo lo ejecutaremos una vez. Después usaremos la selección de registros almacenada en el fichero Rdata.

# Fuente de datos http://cran-logs.rstudio.com/
mydf <- read_csv("./data/2024-03-12.csv")

set.seed(seed=14032024)
N<-200000
I<-sample(1:nrow(mydf),size=N,replace=FALSE)
mydf<-mydf[I,]
# Guardamos en formato binario, la primera vez que cargamos
save(file = './data/2024-03-12.Rdata',list=c('mydf'))

Después de la primera carga poner eval=FALSE en el chunk anterior para acelerar la ejecución.

# Cargamos el fichero binario
load(file ='./data/2024-03-12.Rdata')
dim(mydf)
[1] 200000     10
head(mydf)

mydf %>% head
mydf %>% tail
mydf %>% str
tibble [200,000 × 10] (S3: tbl_df/tbl/data.frame)
 $ date     : Date[1:200000], format: "2024-03-12" "2024-03-12" ...
 $ time     : 'hms' num [1:200000] 48036 63761 70942 43440 ...
  ..- attr(*, "units")= chr "secs"
 $ size     : num [1:200000] 103412 526554 1280374 375281 160694 ...
 $ r_version: chr [1:200000] "4.3.3" "4.3.3" "4.1.2" NA ...
 $ r_arch   : chr [1:200000] "x86_64" "x86_64" "x86_64" NA ...
 $ r_os     : chr [1:200000] "mingw32" "mingw32" "linux-gnu" NA ...
 $ package  : chr [1:200000] "callr" "jquerylib" "colourpicker" "devtools" ...
 $ version  : chr [1:200000] "3.7.5" "0.1.4" "1.3.0" "2.4.5" ...
 $ country  : chr [1:200000] NA "AT" "ET" "GB" ...
 $ ip_id    : num [1:200000] 27 22280 16115 3 6190 ...
mydf %>% dim
[1] 200000     10
  1. Para facilitar el manejo transforma los datos en un tibble as_tibble y almacena el resultado con el nombre cran, y borra el data frame original (usa rm)
# Transformar el data frame en un tibble
cran <- as_tibble(mydf)

# Eliminar el data frame original para liberar memoria
rm(mydf)
  1. Una ventaja de trasformar en tibble es la visualización. Escribe directamente el nombre de la variable y observa la visualización. Posteriormente usa la función glimpse sobre cran y observa el resultado.
# Visualizar el tibble directamente
cran

# Usar la función glimpse para obtener un resumen detallado
glimpse(cran)
Rows: 200,000
Columns: 10
$ date      <date> 2024-03-12, 2024-03-12, 2024-03-12, 2024-03-12, 2024-03-12, 2024…
$ time      <hms> 48036 secs, 63761 secs, 70942 secs, 43440 secs, 5385 secs, 1810 s…
$ size      <dbl> 103412, 526554, 1280374, 375281, 160694, 227646, 1358663, 200365,…
$ r_version <chr> "4.3.3", "4.3.3", "4.1.2", NA, NA, "4.2.0", "4.3.3", "4.3.3", "4.…
$ r_arch    <chr> "x86_64", "x86_64", "x86_64", NA, NA, "x86_64", "x86_64", "x86_64…
$ r_os      <chr> "mingw32", "mingw32", "linux-gnu", NA, NA, "mingw32", "mingw32", …
$ package   <chr> "callr", "jquerylib", "colourpicker", "devtools", "glue", "magrit…
$ version   <chr> "3.7.5", "0.1.4", "1.3.0", "2.4.5", "1.7.0", "2.0.3", "0.5.2", "1…
$ country   <chr> NA, "AT", "ET", "GB", "US", "CN", NA, "US", NA, "KR", "US", "US",…
$ ip_id     <dbl> 27, 22280, 16115, 3, 6190, 42486, 27, 8763, 27, 20, 412, 2157, 27…

3 dplyr

No almacenes el resultado de las operaciones, salvo que sea necesario

  1. Elige únicamente las variables ip_id, package, country
# Seleccionar solo las columnas ip_id, package y country
cran_selected <- cran %>%
  select(ip_id, package, country)

# Ver el resultado
head(cran_selected)
NA
  1. Elige todas las variables entre r_arch y country

cran_range <- cran %>%
  select(r_arch:country)

head(cran_range)
NA
  1. Puedes usar el signo (-) para no coger una columna determinada. Selecciona todas las columnas menos las que están entre date y size
cran_excluded <- cran %>%
  select(-(date:size))

head(cran_excluded)
NA
  1. Elige aquellos registros en los que se haya descargado el paquete plotly, desde un sistema que ejecuta la versión de R 3.5.1
cran_plotly <- cran %>%
  filter(package == "plotly", r_version == "3.5.1")

head(cran_plotly)
NA
  1. Elige aquellas descargas realizadas desde España (ES), con versiones de R superiores a la 3.4.0
cran_es <- cran %>%
  filter(country == "ES", r_version > "3.4.0")

head(cran_es)
NA
  1. Elige aquellas descargas realizadas desde España (ES), o Portugal (PT).
cran_es_pt <- cran %>%
  filter(country %in% c("ES", "PT"))

head(cran_es_pt)
NA
  1. Elige aquellas descargas cuyo tamaño supera los 100500 bytes desde un sistema operativo linux-gnu.
cran_tamano <- cran %>%
  filter(size > 100500, r_os == "linux-gnu")

cran_tamano
  1. Elige aquellas descargas en las que la versión del sistema operativo no está vacía.
descargas_filtradas <- cran %>%
  filter(if_all(everything(), ~ . != ""))
descargas_filtradas
  1. Almacena en cran2 todas las columnas entre size y ip_id.
cran2 <- cran%>%
  select(size:ip_id)
cran2
  1. Ordena cran2 en orden ascendente según la variable ip_id.
cran2_asc <- cran2 %>%
  arrange(ip_id)
cran2_asc
  1. Ordena cran2 en orden descendente según la variable ip_id.
cran2_desc <- cran2 %>%
  arrange(desc(ip_id))
cran2_desc
  1. Ordena cran2 según los valores de package e ip_id.
cran2_ordenada <- cran2 %>%
  arrange(package, ip_id)
cran2_ordenada
  1. Ordena cran2 según los valores de country (ascendente), r_version (desdencente) e ip_id(ascendente).
cran2_ordenada2 <- cran2 %>%
  arrange(desc(r_version), country, ip_id)
cran2_ordenada2
NA
  1. Selecciona el subconjunto formado por las variables, del cran, ip_id, package y size en este orden y almacénalas en cran3.
cran3 <- cran %>%
  select(ip_id, package, size)
  1. Crea una nueva variable en cran3 llamada size_mb que contenga el tamaño de la descarga expresado en Mbytes (\(1Mbyte=2^{20}\) bytes).
cran3 <- cran3%>%
  mutate((size_mb = size / 2^20))
cran3
NA
  1. Sabiendo que 1Gb son \(2^{10}\) Mbytes, crea una nueva variable en cran3 llamada size_gb que contenga el tamaño de la descarga expresado en Gbytes.
cran3 <- cran3 %>%
  mutate(size_gb = size / (2^20 * 2^10))
cran3
  1. Considera que se ha detectado un error en el sistema donde todos los tamaños de las descargas son 1000 bytes menores de lo que corresponde. Crea una nueva variable llamada correct_size con el valor correcto.
cran3 <- cran3 %>%
  mutate(correct_size = size + 1000)
cran3
  1. Usa summarise para determinar el valor medio del tamaño de las descargas realizadas en cran y almacenálo en la variable avg_bytes
avg_bytes <- cran %>%
  summarise(avg_bytes = mean(size, na.rm = TRUE))
avg_bytes

4 Agrupamientos

  1. Crea un agrupamiento del tibble cran según la variable package y almacénalo en la variable by_package. Observa el nuevo elemento creado y aunque aparentemente no hay cambio al principio, se indica que se ha hecho un agrupamiento. Éste agrupamiento se puede deshacer con la función ungroup.
by_package <- cran %>%
  group_by(package)
 by_package

by_package_ungrouped <- by_package %>%
  ungroup()
  1. Usa la función summarise sobre el elemento agrupado y calcula el valor medio de la variable size. Observa el efecto del agrupamiento en el resultado.
  1. Para el agrupamiento creado calcula:

número de elementos; n(), elementos distintos,n_distinct() de la variables ip_id y country, y el la mediana (función stats::median) de la variable size y almacénalos en las variables count, unique, countries y med_bytes respectivamente. El resultado del sumario se almacenará en pack_sum.

  1. Muestra el 1% de los paquetes que han sido más descargados. Para ello puedes utilizar la función quantile(????, probs=0.99). Siendo ??? la variable de interés, que te permitirá estimar el umbral. Los valores por encima serán los que nos interesen. Almacena estos paquetes top descargas en la variable top_counts. NOTA: De ahora en adelante concatena todas las operaciones partiendo del elemento cran, usando pipes.
  1. Repite el apartado anterior utilizando pipes para que finalmente el resultado se ordene, en orden descendente, según el número de descargas (count) y se almacene el la variable top_counts_sorted
  1. En lugar de utilizar el recuento de descargas, es más lógico considerar el número de ordenadores distintos en los que se han descargado las librerías, para ello utilizaríamos la columna unique que cuenta solo una descarga por ordenador, independientemente del número de veces que se descarge una librería. Repite los cálculos considerando esta variable y almacena el resultado en top_unique_sorted.
  1. Completa lo que se indica usando pipes. Selecciona las columnas ip_id, country, package, size de cran y muestra el resultado por pantalla usando print. No es necesario almacenar.
  1. Añade una columna llamada size_mb que contenga el tamaño de cada descarga en megabytes.
  1. Selecciona aquellas descargas cuyo tamaño es menor o igual que 0.5 Mbytes
  1. Repite el apartado anterior ordenando los resultados, en orden descendente según el tamaño en Megabytes y muéstralo por pantalla.

EXTRA. Se desea mostar gráficamente aquellos países que suponen el mayor tráfico de información, ocasionado por el total de descargas acumuladas. Para facilitar la visualización céntrate en aquellos que están por encima del 90 % del top de descargas. El gráfico debe mostrar los valores de forma ordenada de mayor a menor. En el eje X debe aparecer el código de país y en el eje Y el tráfico generado. Intenta hacerlo usando una tubería sin emplear ninguna variable auxiliar. cran%>%...+geom_point()

EXTRA Tal como se indica en el ejercicio, el significado de los códigos usados para la codificación de los paise se pueden obtener de https://dev.maxmind.com/geoip/geoip2/geolite2/. Descarga el fichero GeoLite2 Country en formato CSV. Este fichero contiene información en varios idiomas. Usa GeoLite2-Country-Locations-es.csv que tiene la información en español. Deseamos poder determinar las descargas por continentes por lo que necesitamos añadir esta información a los datos de las descargas almacenados en cran. Procede de la siguiente forma:

5 tidyr.

Carga el fichero de datos datostidyr.Rdata que contiene varios conjuntos de datos que vamos a usar en los siguientes ejercicios.

  1. Observa el data frame students : grade male female 1 A 1 5 2 B 5 0 3 C 5 2 4 D 5 5 5 E 7 4

La columna grade es la calificación y las dos siguientes el número de estudiantes hombre y mujer que han recibido dicha calificación. ¿ Cuáles son las variables ?. Transfórmalo en un conjunto tidy con las variables, grade, sex y count

  1. Considera el conjunto ´students2´, que es similar al primero pero que se han recogido datos correspondientes a dos grupos, (male_1, female_1, etc). Transfórmalo en un conjunto tidy añadiendo una columna adicional correspondiente al grupo (llama a la nueva variable class)
  1. Considera el conjunto students3 y observa por qué no es un tidy data . Las variables finales del conjunto deberían ser: name, class, midterm, final. Dado que un alumno solo puede estar en una de las clases aparecen muchos NA, al reorganizar, usa na.rm=TRUE en las opciones de gather
  1. Considera que la columna class queremos especificarla con valores, 1,2,3,4 y 5 en lugar de class1, …, class5. En librería de importación de datos (readr) se proporcionan funciones que permite identificar y extraer diversos tipos de datos. Son las funciones parse_XXXX. Observa el efecto de aplicar parse_number('class5') y utiliza el resultado para realizar la tarea solicitada.
  1. Observa el conjunto students4 donde se presenta el problema de tener varias observaciones en la misma tabla. Separa el conjunto en dos tablas, una que contenga información relativa a los estudiantes, que llamarás student_info, que no debe contener repeticiones, y otra con las calificaciones y la clave primaria que enlaza ambas tablas(id), que llamarás gradebook.
  1. Consideremos el caso de una observación almacenada en varias tablas. Observa los datos almacenados en failed y passed con la información de los alumnos que suspeden (calificaciones C,D,E,F) y los que aprueban (calificaciones A, B). Debes unir ambos conjuntos en uno que contenga una columna adicional llamada status que indicará passed o failed según la situación de cada alumno, puedes usar la función bind_rows.

6 Pongamos todo en común.

  1. El SAT es un examen popular de preparación para la universidad en los Estados Unidos que consta de tres partes: lectura crítica, matemáticas y escritura. Los estudiantes pueden obtener hasta 800 puntos en cada parte. Este conjunto de datos presenta el número total de estudiantes, para cada combinación de parte y sexo del estudiante. Considera los datos almacenado en sat y haz las transformaciones adecuadas para que las columnas del conjunto tidy sean score_range, part, sex, count en ese orden. Ten en cuenta que los datos totales no es necesario almacenarlos ya que se pueden generar a posteriori.
  1. Utiliza agrupamientos, según las variables part, sex y genera dos nuevas columnas que contengan el número total de estudiantes en dicho grupo, y la proporción dentro del grupo. Almacénalas en las variables total y prop respectivamente.
  1. Representa gráficamente el conjunto obtenido para mostrar las diferencias entre hombres y mujeres, en las diferentes pruebas para cada una de las franjas de calificación.

7 Manejo de fechas con lubridate.

  1. La función today devuelve la fecha actual. Almacéna la fecha actual en this_day, y muéstrala por pantalla. Usa las funciones year, month y day para extraer cada una de las partes. wday para obtener el día de la semana ()
  1. Guarda el instante actual en una variable llamada this_moment con la función now y muéstralo por pantalla. Usa las funciónes hour, minute, second para extraer la información de cada elemento.
  1. La librería lubridate dispone de múltiples funciones para importar en formato fecha datos con un gran variedad de formatos de entrada ymd(), dmy(), hms(), ymd_hms() donde cada letra indica years (y),months (m), days (d), hours (h), minutes (m), y seconds (s). Prueba my_date <- ymd(“2019-02-14”), y determina de que clase es my_date. Las funciones son muy robustas y pueden intepretar correctamente diversos formato. Prueba ymd() para importar la fecha “1989 May 17”. ¿Cómo importarías “March 12, 1975”?
  1. Importa el valor numérico 25081985, sabiendo que el formato es dia, mes, año, y posteriormente 192012 donde se ha almacenado año, mes y día. Modifica el campo fecha en el segundo caso añadiendo algún carácter separador para eliminar la ambigüedad.
fecha<-'19200102'
ymd(fecha)
  1. Almacena la cadena 2014-08-23 17:23:02 en la variable dt1 e impórtala para que incluya la fecha y la hora,posteriormente importa la cadena "03:22:14" (hh:mm:ss)
  1. Importa el vector de fechas almacenado el dt2
  1. Podemos reasingar nuevos valores a una variable fecha/hora con la función update , por ejemplo: update(this_moment, hours = 8, minutes = 34, seconds = 55). Actualiza la variable this_moment creada anteriormente para que contenga la hora actual.
  1. Zonas horarias. Considera que estás en Nueva York, que planeas visitar a un amigo en Hong Kong y que has perdido tu itinerario, pero sabes que tu vuelo sale de Nueva York a las 17:34 (17:34 horas) pasado mañana. También sabes que el vuelo está programado para llegar a Hong Kong exactamente 15 horas y 50 minutos después de la salida. Vamos a reconstruir el itinerario:
LS0tDQp0aXRsZTogIkNhcnJlcmEgVGlkeXZlcnNlLiBQcmVndW50YXMgQ29ydGFzLk1BUkNFTElOTyBNQVJUSU5FWiINCnN1YnRpdGxlOiBUcmF0YW1pZW50byBkZSBEYXRvcy4gR3JhZG8gZW4gQ2llbmNpYSBkZSBEYXRvcy0gVVYNCmF1dGhvcjogIk1hcmNlbGlubyBNYXJ0w61uZXoiDQpkYXRlOiAgImByIFN5cy5EYXRlKClgIiAgI1BvbmRyw61hIGxhIGZlY2hhIGRlbCBkw61hIGFjdHVhbA0KcGFyYW1zOg0KICBsYW5nOiBFUw0KbGFuZzogImByIHN3aXRjaChwYXJhbXMkbGFuZywgRVMgPSAnZXMtRVMnLCBFTiA9ICdlbi1VUycpYCINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50Og0KICAgIHRvYzogbm8NCiAgICB0b2NfZGVwdGg6IDMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICBodG1sX2RvY3VtZW50Og0KICAgIGVjaG86IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6IGx1bWVuDQogICAgdG9jOiB5ZXMNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBlY2hvOiB5ZXMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRvYzogeWVzDQpsYW5ndWFnZToNCiAgbGFiZWw6DQogICAgZmlnOiAnRmlndXJhICcNCiAgICB0YWI6ICdUYWJsYSAnDQogICAgZXE6ICdFY3VhY2nDs24gJw0KICAgIHRobTogJ1Rlb3JlbWEgJw0KICAgIGxlbTogJ0xlbWEgJw0KICAgIGRlZjogJ0RlZmluaWNpw7NuICcNCiAgICBjb3I6ICdDb3JvbGFyaW8gJw0KICAgIHBycDogJ1Byb3Bvc2ljacOzbiAnDQogICAgZXhtOiAnRWplbXBsbyAnDQogICAgZXhyOiAnRWplcmNpY2lvICcNCiAgICBwcm9vZjogJ0RlbW9zdHJhY2nDs24uICcNCiAgICByZW1hcms6ICdOb3RhOiAnDQogICAgc29sdXRpb246ICdTb2x1Y2nDs24uICcNCi0tLQ0KDQoNCmBgYHtyIHNldHVwLCBjYWNoZSA9IEYsIGVjaG8gPSBGLCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEYsIHRpZHkgPSBGLH0NCg0KIyBDT05GSUdVUkFDScOTTiBHRU5FUkFMDQpsaWJyYXJ5KGtuaXRyKQ0Kb3B0aW9ucyh3aWR0aCA9IDEwMCkNCiMgT3BjaW9uZXMgZ2VuZXJhbGVzIGNodW5rcw0KDQojIFBBUkEgR0VORVJBUiBTT0xPIExPUyBFTlVOQ0lBRE8gaW5jbHVkZT1GQUxTRQ0Kb3B0c19jaHVuayRzZXQoZWNobz1GLG1lc3NhZ2UgPSBGLCBlcnJvciA9IEYsIHdhcm5pbmcgPSBGLCBjb21tZW50ID0gTkEsIGZpZy5hbGlnbiA9ICdjZW50ZXInLCBkcGkgPSAxMDAsIHRpZHkgPSBGLCBjYWNoZS5wYXRoID0gJy5jYWNoZS8nLCBmaWcucGF0aCA9ICcuL2ZpZ3VyZS8nLCBpbmNsdWRlPUZBTFNFLGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTgpDQoNCiMgUEFSQSBJbmNsdWlyIGxhIHNvbHVjaW9uZXMgU09MTyBMT1MgRU5VTkNJQURPIGluY2x1ZGU9VFJVRQ0Kb3B0c19jaHVuayRzZXQoZWNobz1ULG1lc3NhZ2UgPSBGLCBlcnJvciA9IEYsIHdhcm5pbmcgPSBGLCBjb21tZW50ID0gTkEsIGZpZy5hbGlnbiA9ICdjZW50ZXInLCBkcGkgPSAxMDAsIHRpZHkgPSBGLCBjYWNoZS5wYXRoID0gJy5jYWNoZS8nLCBmaWcucGF0aCA9ICcuL2ZpZ3VyZS8nLCBpbmNsdWRlPVRSVUUsZmlnLmhlaWdodD02LCBmaWcud2lkdGg9OCkNCg0KDQpgYGANCg0KIyBVc2EgYHBhY21hbmAgcGFyYSBjYXJnYXIgbGFzIGxpYnJlcsOtYXMgZSBpbnN0YWxhcmxhcyBzaSBubyBlc3TDoW4gZGlzcG9uaWJsZXMNCg0KYGBge3IsZWNobz1GQUxTRX0NCg0KbGlicmFyeShwYWNtYW4pDQpwX2xvYWQoZHBseXIsbHVicmlkYXRlKQ0KDQpgYGANCg0KVG9kb3MgbG9zIGVqZXJjaWNpb3MgcGxhbnRlYWRvcyBlc3TDoW4gcmVsYWNpb25hZG9zIGNvbiBlbCB1c28gZGUgbGFzIGxpYnJlcsOtYXMgKipkcGx5ciwgdGlkeXIgeSBsdWJyaWRhdGUqKi4gRmFtaWxpYXLDrXphdGUgY29uIGVsIHVzbyBkZWwgb3BlcmFkb3IgKiolPiUqKiBwYXJhIGZhY2lsaXRhciBsYSBjb21wcmVzacOzbiBkZWwgY8OzZGlnby4NCkxvcyBlamVyY2ljaW9zIGNvbnNpZGVyYW4gcXVlIGxhcyBsaWJyZXLDrWEgZXN0w6FuIGluc3RhbGFkYXMgeSBjYXJnYWRhcy4gIA0KDQojIEZpY2hlcm8gZGUgZGF0b3MuDQoNClRvZGFzIGxhcyBwcmVndW50YXMgZXN0w6FuIHJlZmVyaWRhcyBhbCBmaWNoZXJvICoqMjAyNC0wMy0xMi5SZGF0YSoqLCBxdWUgY29udGllbmUgaW5mb3JtYWNpw7NuIGRlIGxvcyByZWdpc3Ryb3MgKGxvZ3MpIGRlIGxhcyBkZXNjYXJnYXMgcmVhbGl6YWRhcyBkZWwgcmVwb3NpdG9yaW8gQ1JBTiBlbiBlc2EgZmVjaGEuIExhIGluZm9ybWFjacOzbiBjb250ZW5pZGEgZW4gZWwgZmljaGVybyBlcyBsYSBzaWd1aWVudGU6DQoNCg0KKiBkYXRlDQoqIHRpbWUgKGluIFVUQykNCiogc2l6ZSAoaW4gYnl0ZXMpDQoqIHJfdmVyc2lvbiwgdmVyc2lvbiBvZiBSIHVzZWQgdG8gZG93bmxvYWQgcGFja2FnZQ0KKiByX2FyY2ggKGkzODYgPSAzMiBiaXQsIHg4Nl82NCA9IDY0IGJpdCkNCiogcl9vcyAoZGFyd2luOS44LjAgPSBtYWMsIG1pbmd3MzIgPSB3aW5kb3dzKQ0KKiBwYWNrYWdlDQoqIGNvdW50cnksIHR3byBsZXR0ZXIgSVNPIGNvdW50cnkgY29kZS4gR2VvY29kZWQgZnJvbSBJUCB1c2luZyBbTWF4TWluZCdzXVtodHRwczovL2Rldi5tYXhtaW5kLmNvbS9nZW9pcC9nZW9pcDIvZ2VvbGl0ZTIvXSBmcmVlIGRhdGFiYXNlDQoqIGlwX2lkLCBhIGRhaWx5IHVuaXF1ZSBpZCBhc3NpZ25lZCB0byBlYWNoIElQIGFkZHJlc3MNCg0KRWwgbWF0ZXJpYWwgZXN0w6EgYmFzYWRvIGVuIGVsIGN1cnNvICdHZXR0aW5nIGFuZCBDbGVhbmluZyBkYXRhJyBkZSBsYSBsaWJyZXLDrWEgW3N3aXJscl0oaHR0cHM6Ly9zd2lybHN0YXRzLmNvbS8pIA0KDQpTZSBwdWVkZW4gZGVzY2FyZ2FyIGZpY2hlcm9zIGFjdHVhbGVzIGRlbCBgbG9nc2AgdXNhbmRvIGxhIGxpYnJlcsOtYSBodHRwczovL3ItaHViLmdpdGh1Yi5pby9jcmFubG9ncy8NCg0KMS4gIENhcmdhIGVsIGZpY2hlcm8gZGUgZGF0b3MgeSBhbG1hY8OpbmFsbyBlbiB1biBkYXRhIGZyYW1lIGxsYW1hZG8gYG15ZGZgLiBBc2Vnw7pyYXRlIHF1ZSBsYSBvcGNpw7NuIGBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFYC4gTWlyYSBsYXMgZGltZW5zaW9uZXMgeSBsb3MgcHJpbWVyb3MgZGF0b3MuDQoNCioqTm90YSoqOiBFbCBzaWd1aWVudGUgYmxvcXVlIHB1ZWRlIHRhcmRhciBlbiBlamVjdXRhcnNlIHBvciBsbyBxdWUgc29sbyBsbyBlamVjdXRhcmVtb3MgdW5hIHZlei4gRGVzcHXDqXMgdXNhcmVtb3MgbGEgc2VsZWNjacOzbiBkZSByZWdpc3Ryb3MgYWxtYWNlbmFkYSBlbiBlbCBmaWNoZXJvIFJkYXRhLg0KDQoNCmBgYHtyLGV2YWw9RkFMU0V9DQojIEZ1ZW50ZSBkZSBkYXRvcyBodHRwOi8vY3Jhbi1sb2dzLnJzdHVkaW8uY29tLw0KbXlkZiA8LSByZWFkX2NzdigiLi9kYXRhLzIwMjQtMDMtMTIuY3N2IikNCg0Kc2V0LnNlZWQoc2VlZD0xNDAzMjAyNCkNCk48LTIwMDAwMA0KSTwtc2FtcGxlKDE6bnJvdyhteWRmKSxzaXplPU4scmVwbGFjZT1GQUxTRSkNCm15ZGY8LW15ZGZbSSxdDQojIEd1YXJkYW1vcyBlbiBmb3JtYXRvIGJpbmFyaW8sIGxhIHByaW1lcmEgdmV6IHF1ZSBjYXJnYW1vcw0Kc2F2ZShmaWxlID0gJy4vZGF0YS8yMDI0LTAzLTEyLlJkYXRhJyxsaXN0PWMoJ215ZGYnKSkNCmBgYA0KDQpEZXNwdcOpcyBkZSBsYSBwcmltZXJhIGNhcmdhIHBvbmVyICoqZXZhbD1GQUxTRSoqIGVuIGVsICpjaHVuayogYW50ZXJpb3IgcGFyYSBhY2VsZXJhciBsYSBlamVjdWNpw7NuLg0KDQpgYGB7cn0NCiMgQ2FyZ2Ftb3MgZWwgZmljaGVybyBiaW5hcmlvDQpsb2FkKGZpbGUgPScuL2RhdGEvMjAyNC0wMy0xMi5SZGF0YScpDQpkaW0obXlkZikNCmhlYWQobXlkZikNCg0KbXlkZiAlPiUgaGVhZA0KbXlkZiAlPiUgdGFpbA0KbXlkZiAlPiUgc3RyDQpteWRmICU+JSBkaW0NCg0KYGBgDQoyLiBQYXJhIGZhY2lsaXRhciBlbCBtYW5lam8gdHJhbnNmb3JtYSBsb3MgZGF0b3MgZW4gdW4gdGliYmxlIGBhc190aWJibGVgIHkgYWxtYWNlbmEgZWwgcmVzdWx0YWRvIGNvbiBlbCBub21icmUgYGNyYW5gLCB5IGJvcnJhIGVsIGRhdGEgZnJhbWUgb3JpZ2luYWwgKHVzYSBgcm1gKQ0KYGBge3J9DQojIFRyYW5zZm9ybWFyIGVsIGRhdGEgZnJhbWUgZW4gdW4gdGliYmxlDQpjcmFuIDwtIGFzX3RpYmJsZShteWRmKQ0KDQojIEVsaW1pbmFyIGVsIGRhdGEgZnJhbWUgb3JpZ2luYWwgcGFyYSBsaWJlcmFyIG1lbW9yaWENCnJtKG15ZGYpDQoNCg0KYGBgDQozLiBVbmEgdmVudGFqYSBkZSB0cmFzZm9ybWFyIGVuIHRpYmJsZSBlcyBsYSB2aXN1YWxpemFjacOzbi4gRXNjcmliZSBkaXJlY3RhbWVudGUgZWwgbm9tYnJlIGRlIGxhIHZhcmlhYmxlIHkgb2JzZXJ2YSBsYSB2aXN1YWxpemFjacOzbi4gUG9zdGVyaW9ybWVudGUgdXNhIGxhIGZ1bmNpw7NuIGBnbGltcHNlYCBzb2JyZSBgY3JhbmAgeSBvYnNlcnZhIGVsIHJlc3VsdGFkby4NCmBgYHtyfQ0KIyBWaXN1YWxpemFyIGVsIHRpYmJsZSBkaXJlY3RhbWVudGUNCmNyYW4NCg0KIyBVc2FyIGxhIGZ1bmNpw7NuIGdsaW1wc2UgcGFyYSBvYnRlbmVyIHVuIHJlc3VtZW4gZGV0YWxsYWRvDQpnbGltcHNlKGNyYW4pDQoNCg0KYGBgDQojIGRwbHlyDQoqKk5vIGFsbWFjZW5lcyBlbCByZXN1bHRhZG8gZGUgbGFzIG9wZXJhY2lvbmVzLCBzYWx2byBxdWUgc2VhIG5lY2VzYXJpbyoqDQoNCjQuIEVsaWdlIMO6bmljYW1lbnRlIGxhcyB2YXJpYWJsZXMgYGlwX2lkLCBwYWNrYWdlLCBjb3VudHJ5YA0KYGBge3J9DQojIFNlbGVjY2lvbmFyIHNvbG8gbGFzIGNvbHVtbmFzIGlwX2lkLCBwYWNrYWdlIHkgY291bnRyeQ0KY3Jhbl9zZWxlY3RlZCA8LSBjcmFuICU+JQ0KICBzZWxlY3QoaXBfaWQsIHBhY2thZ2UsIGNvdW50cnkpDQoNCiMgVmVyIGVsIHJlc3VsdGFkbw0KaGVhZChjcmFuX3NlbGVjdGVkKQ0KDQpgYGANCjUuIEVsaWdlIHRvZGFzIGxhcyB2YXJpYWJsZXMgZW50cmUgYHJfYXJjaCB5IGNvdW50cnlgIA0KYGBge3J9DQoNCmNyYW5fcmFuZ2UgPC0gY3JhbiAlPiUNCiAgc2VsZWN0KHJfYXJjaDpjb3VudHJ5KQ0KDQpoZWFkKGNyYW5fcmFuZ2UpDQoNCmBgYA0KNS4gUHVlZGVzIHVzYXIgZWwgc2lnbm8gKC0pIHBhcmEgbm8gY29nZXIgdW5hIGNvbHVtbmEgZGV0ZXJtaW5hZGEuIFNlbGVjY2lvbmEgdG9kYXMgbGFzIGNvbHVtbmFzIG1lbm9zIGxhcyBxdWUgZXN0w6FuIGVudHJlIGBkYXRlIHkgc2l6ZWAgDQpgYGB7cn0NCmNyYW5fZXhjbHVkZWQgPC0gY3JhbiAlPiUNCiAgc2VsZWN0KC0oZGF0ZTpzaXplKSkNCg0KaGVhZChjcmFuX2V4Y2x1ZGVkKQ0KDQpgYGANCjYuIEVsaWdlIGFxdWVsbG9zIHJlZ2lzdHJvcyBlbiBsb3MgcXVlIHNlIGhheWEgZGVzY2FyZ2FkbyBlbCBwYXF1ZXRlIGBwbG90bHlgLCBkZXNkZSB1biBzaXN0ZW1hIHF1ZSBlamVjdXRhIGxhIHZlcnNpw7NuIGRlIFIgMy41LjENCmBgYHtyfQ0KY3Jhbl9wbG90bHkgPC0gY3JhbiAlPiUNCiAgZmlsdGVyKHBhY2thZ2UgPT0gInBsb3RseSIsIHJfdmVyc2lvbiA9PSAiMy41LjEiKQ0KDQpoZWFkKGNyYW5fcGxvdGx5KQ0KDQpgYGANCjYuIEVsaWdlIGFxdWVsbGFzIGRlc2NhcmdhcyByZWFsaXphZGFzIGRlc2RlIEVzcGHDsWEgKEVTKSwgY29uIHZlcnNpb25lcyBkZSBSIHN1cGVyaW9yZXMgYSBsYSAzLjQuMA0KYGBge3J9DQpjcmFuX2VzIDwtIGNyYW4gJT4lDQogIGZpbHRlcihjb3VudHJ5ID09ICJFUyIsIHJfdmVyc2lvbiA+ICIzLjQuMCIpDQoNCmhlYWQoY3Jhbl9lcykNCg0KYGBgDQoNCjcuIEVsaWdlIGFxdWVsbGFzIGRlc2NhcmdhcyByZWFsaXphZGFzIGRlc2RlIEVzcGHDsWEgKEVTKSwgbyBQb3J0dWdhbCAoUFQpLg0KYGBge3J9DQpjcmFuX2VzX3B0IDwtIGNyYW4gJT4lDQogIGZpbHRlcihjb3VudHJ5ICVpbiUgYygiRVMiLCAiUFQiKSkNCg0KaGVhZChjcmFuX2VzX3B0KQ0KDQpgYGANCg0KOC4gRWxpZ2UgYXF1ZWxsYXMgZGVzY2FyZ2FzIGN1eW8gdGFtYcOxbyBzdXBlcmEgbG9zIDEwMDUwMCBieXRlcyBkZXNkZSB1biBzaXN0ZW1hIG9wZXJhdGl2byAgYGxpbnV4LWdudWAuDQpgYGB7cn0NCmNyYW5fdGFtYW5vIDwtIGNyYW4gJT4lDQogIGZpbHRlcihzaXplID4gMTAwNTAwLCByX29zID09ICJsaW51eC1nbnUiKQ0KDQpjcmFuX3RhbWFubw0KYGBgDQo5LiBFbGlnZSBhcXVlbGxhcyBkZXNjYXJnYXMgZW4gbGFzIHF1ZSBsYSB2ZXJzacOzbiBkZWwgc2lzdGVtYSBvcGVyYXRpdm8gbm8gZXN0w6EgdmFjw61hLg0KYGBge3J9DQpkZXNjYXJnYXNfZmlsdHJhZGFzIDwtIGNyYW4gJT4lDQogIGZpbHRlcihpZl9hbGwoZXZlcnl0aGluZygpLCB+IC4gIT0gIiIpKQ0KZGVzY2FyZ2FzX2ZpbHRyYWRhcw0KYGBgDQoxMC4gQWxtYWNlbmEgZW4gYGNyYW4yYCB0b2RhcyBsYXMgY29sdW1uYXMgZW50cmUgYHNpemVgIHkgYGlwX2lkYC4gDQpgYGB7cn0NCmNyYW4yIDwtIGNyYW4lPiUNCiAgc2VsZWN0KHNpemU6aXBfaWQpDQpjcmFuMg0KYGBgDQoxMS4gT3JkZW5hIGBjcmFuMmAgZW4gb3JkZW4gYXNjZW5kZW50ZSBzZWfDum4gbGEgdmFyaWFibGUgYGlwX2lkYC4NCmBgYHtyfQ0KY3JhbjJfYXNjIDwtIGNyYW4yICU+JQ0KICBhcnJhbmdlKGlwX2lkKQ0KY3JhbjJfYXNjDQpgYGANCjEyLiBPcmRlbmEgYGNyYW4yYCBlbiBvcmRlbiBkZXNjZW5kZW50ZSBzZWfDum4gbGEgdmFyaWFibGUgYGlwX2lkYC4NCmBgYHtyfQ0KY3JhbjJfZGVzYyA8LSBjcmFuMiAlPiUNCiAgYXJyYW5nZShkZXNjKGlwX2lkKSkNCmNyYW4yX2Rlc2MNCmBgYA0KMTIuIE9yZGVuYSBgY3JhbjJgIHNlZ8O6biBsb3MgdmFsb3JlcyBkZSBgcGFja2FnZSBlIGlwX2lkYC4NCmBgYHtyfQ0KY3JhbjJfb3JkZW5hZGEgPC0gY3JhbjIgJT4lDQogIGFycmFuZ2UocGFja2FnZSwgaXBfaWQpDQpjcmFuMl9vcmRlbmFkYQ0KYGBgDQoxMy4gT3JkZW5hIGBjcmFuMmAgc2Vnw7puIGxvcyB2YWxvcmVzIGRlIGBjb3VudHJ5YCAoYXNjZW5kZW50ZSksIGByX3ZlcnNpb25gIChkZXNkZW5jZW50ZSkgZSBgaXBfaWRgKGFzY2VuZGVudGUpLg0KYGBge3J9DQpjcmFuMl9vcmRlbmFkYTIgPC0gY3JhbjIgJT4lDQogIGFycmFuZ2UoZGVzYyhyX3ZlcnNpb24pLCBjb3VudHJ5LCBpcF9pZCkNCmNyYW4yX29yZGVuYWRhMg0KDQpgYGANCjE0LiBTZWxlY2Npb25hIGVsIHN1YmNvbmp1bnRvIGZvcm1hZG8gcG9yIGxhcyB2YXJpYWJsZXMsIGRlbCBgY3JhbmAsIGBpcF9pZCwgcGFja2FnZSB5IHNpemVgIA0KZW4gZXN0ZSBvcmRlbiB5IGFsbWFjw6luYWxhcyBlbiBgY3JhbjNgLg0KYGBge3J9DQpjcmFuMyA8LSBjcmFuICU+JQ0KICBzZWxlY3QoaXBfaWQsIHBhY2thZ2UsIHNpemUpDQoNCmBgYA0KDQoxNS4gQ3JlYSB1bmEgbnVldmEgdmFyaWFibGUgZW4gYGNyYW4zYCBsbGFtYWRhIGBzaXplX21iYCBxdWUgY29udGVuZ2EgZWwgdGFtYcOxbyBkZSBsYSBkZXNjYXJnYSBleHByZXNhZG8gZW4gTWJ5dGVzICgkMU1ieXRlPTJeezIwfSQgYnl0ZXMpLg0KYGBge3J9DQpjcmFuMyA8LSBjcmFuMyU+JQ0KICBtdXRhdGUoKHNpemVfbWIgPSBzaXplIC8gMl4yMCkpDQpjcmFuMw0KDQpgYGANCjE2LiBTYWJpZW5kbyBxdWUgMUdiIHNvbiAkMl57MTB9JCBNYnl0ZXMsIGNyZWEgdW5hIG51ZXZhIHZhcmlhYmxlIGVuIGBjcmFuM2AgbGxhbWFkYSBgc2l6ZV9nYmAgcXVlIGNvbnRlbmdhIGVsIHRhbWHDsW8gZGUgbGEgZGVzY2FyZ2EgZXhwcmVzYWRvIGVuIEdieXRlcy4NCmBgYHtyfQ0KY3JhbjMgPC0gY3JhbjMgJT4lDQogIG11dGF0ZShzaXplX2diID0gc2l6ZSAvICgyXjIwICogMl4xMCkpDQpjcmFuMw0KYGBgDQoxNy4gQ29uc2lkZXJhIHF1ZSBzZSBoYSBkZXRlY3RhZG8gdW4gZXJyb3IgZW4gZWwgc2lzdGVtYSBkb25kZSB0b2RvcyBsb3MgdGFtYcOxb3MgZGUgbGFzIGRlc2NhcmdhcyBzb24gMTAwMCBieXRlcyBtZW5vcmVzIGRlIGxvIHF1ZSBjb3JyZXNwb25kZS4gQ3JlYSB1bmEgbnVldmEgdmFyaWFibGUgbGxhbWFkYSBgY29ycmVjdF9zaXplYCBjb24gZWwgdmFsb3IgY29ycmVjdG8uIA0KYGBge3J9DQpjcmFuMyA8LSBjcmFuMyAlPiUNCiAgbXV0YXRlKGNvcnJlY3Rfc2l6ZSA9IHNpemUgKyAxMDAwKQ0KY3JhbjMNCmBgYA0KMTguIFVzYSBgc3VtbWFyaXNlYCBwYXJhIGRldGVybWluYXIgZWwgdmFsb3IgbWVkaW8gZGVsIHRhbWHDsW8gZGUgbGFzIGRlc2NhcmdhcyByZWFsaXphZGFzIGVuIGBjcmFuYCB5IGFsbWFjZW7DoWxvIGVuIGxhIHZhcmlhYmxlIGBhdmdfYnl0ZXNgDQpgYGB7cn0NCmF2Z19ieXRlcyA8LSBjcmFuICU+JQ0KICBzdW1tYXJpc2UoYXZnX2J5dGVzID0gbWVhbihzaXplLCBuYS5ybSA9IFRSVUUpKQ0KYXZnX2J5dGVzDQpgYGANCiMgQWdydXBhbWllbnRvcw0KDQoxOS4gQ3JlYSB1biBhZ3J1cGFtaWVudG8gZGVsIHRpYmJsZSBgY3JhbmAgc2Vnw7puIGxhIHZhcmlhYmxlIGBwYWNrYWdlYCB5IGFsbWFjw6luYWxvIGVuIGxhIHZhcmlhYmxlIGBieV9wYWNrYWdlYC4gT2JzZXJ2YSBlbCBudWV2byBlbGVtZW50byBjcmVhZG8geSBhdW5xdWUgYXBhcmVudGVtZW50ZSBubyBoYXkgY2FtYmlvIGFsIHByaW5jaXBpbywgc2UgaW5kaWNhIHF1ZSBzZSBoYSBoZWNobyB1biBhZ3J1cGFtaWVudG8uIMOJc3RlIGFncnVwYW1pZW50byBzZSBwdWVkZSBkZXNoYWNlciBjb24gbGEgZnVuY2nDs24gYHVuZ3JvdXBgLg0KYGBge3J9DQpieV9wYWNrYWdlIDwtIGNyYW4gJT4lDQogIGdyb3VwX2J5KHBhY2thZ2UpDQogYnlfcGFja2FnZQ0KDQpieV9wYWNrYWdlX3VuZ3JvdXBlZCA8LSBieV9wYWNrYWdlICU+JQ0KICB1bmdyb3VwKCkNCg0KYGBgDQoNCjIwLiBVc2EgbGEgZnVuY2nDs24gYHN1bW1hcmlzZWAgc29icmUgIGVsIGVsZW1lbnRvIGFncnVwYWRvIHkgY2FsY3VsYSAgZWwgdmFsb3IgbWVkaW8gZGUgbGEgdmFyaWFibGUgYHNpemVgLiBPYnNlcnZhIGVsIGVmZWN0byBkZWwgYWdydXBhbWllbnRvIGVuIGVsIHJlc3VsdGFkby4NCmBgYHtyfQ0KDQpgYGANCjIxLiBQYXJhIGVsIGFncnVwYW1pZW50byBjcmVhZG8gY2FsY3VsYTogDQorIC4gY291bnQgPSBuKCkNCisgLiB1bmlxdWUgPSBuX2Rpc3RpbmN0KGlwX2lkKQ0KKyAuIGNvdW50cmllcyA9IG5fZGlzdGluY3QoY291bnRyeSkNCisgLiBhdmdfYnl0ZXMgPSBtZWFuKHNpemUpDQoNCm7Dum1lcm8gZGUgZWxlbWVudG9zOyBgbigpYCwgDQplbGVtZW50b3MgZGlzdGludG9zLGBuX2Rpc3RpbmN0KClgIGRlIGxhIHZhcmlhYmxlcyBgaXBfaWQgeSBjb3VudHJ5YCwgeSBlbCANCmxhIG1lZGlhbmEgKGZ1bmNpw7NuIGBzdGF0czo6bWVkaWFuYCkgZGUgbGEgdmFyaWFibGUgYHNpemVgIHkgYWxtYWPDqW5hbG9zIGVuIGxhcyB2YXJpYWJsZXMgYGNvdW50LCB1bmlxdWUsIGNvdW50cmllcyB5IG1lZF9ieXRlc2AgcmVzcGVjdGl2YW1lbnRlLiBFbCByZXN1bHRhZG8gZGVsIHN1bWFyaW8gc2UgYWxtYWNlbmFyw6EgZW4gYHBhY2tfc3VtYC4NCg0KYGBge3J9DQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KYGBgDQoyMi4gTXVlc3RyYSBlbCAxJSBkZSBsb3MgcGFxdWV0ZXMgcXVlIGhhbiBzaWRvIG3DoXMgZGVzY2FyZ2Fkb3MuIFBhcmEgZWxsbyBwdWVkZXMgdXRpbGl6YXIgbGEgZnVuY2nDs24gYHF1YW50aWxlKD8/Pz8sIHByb2JzPTAuOTkpYC4gU2llbmRvID8/PyBsYSB2YXJpYWJsZSBkZSBpbnRlcsOpcywgcXVlIHRlIHBlcm1pdGlyw6EgZXN0aW1hciBlbCB1bWJyYWwuIExvcyB2YWxvcmVzIHBvciBlbmNpbWEgc2Vyw6FuIGxvcyBxdWUgbm9zIGludGVyZXNlbi4gQWxtYWNlbmEgZXN0b3MgcGFxdWV0ZXMgKip0b3AgZGVzY2FyZ2FzKiogZW4gbGEgdmFyaWFibGUgYHRvcF9jb3VudHNgLiAqKk5PVEE6IERlIGFob3JhIGVuIGFkZWxhbnRlIGNvbmNhdGVuYSB0b2RhcyBsYXMgb3BlcmFjaW9uZXMgcGFydGllbmRvIGRlbCBlbGVtZW50byBgY3JhbmAsIHVzYW5kbyBfcGlwZXNfKiouDQpgYGB7cn0NCg0KDQoNCg0KDQoNCmBgYA0KMjMuIFJlcGl0ZSBlbCBhcGFydGFkbyBhbnRlcmlvciB1dGlsaXphbmRvICoqcGlwZXMqKiBwYXJhIHF1ZSBmaW5hbG1lbnRlIGVsIHJlc3VsdGFkbyBzZSBvcmRlbmUsIGVuIG9yZGVuIGRlc2NlbmRlbnRlLCBzZWfDum4gZWwgbsO6bWVybyBkZSBkZXNjYXJnYXMgKGBjb3VudGApIHkgc2UgYWxtYWNlbmUgZWwgbGEgdmFyaWFibGUgYHRvcF9jb3VudHNfc29ydGVkYA0KYGBge3J9DQoNCg0KDQoNCg0KDQoNCg0KDQpgYGANCg0KMjQuIEVuIGx1Z2FyIGRlIHV0aWxpemFyIGVsIHJlY3VlbnRvIGRlIGRlc2NhcmdhcywgZXMgbcOhcyBsw7NnaWNvIGNvbnNpZGVyYXIgZWwgbsO6bWVybyBkZSBvcmRlbmFkb3JlcyBkaXN0aW50b3MgZW4gbG9zIHF1ZSBzZSBoYW4gZGVzY2FyZ2FkbyBsYXMgbGlicmVyw61hcywgcGFyYSBlbGxvIHV0aWxpemFyw61hbW9zIGxhIGNvbHVtbmEgYHVuaXF1ZWAgcXVlIGN1ZW50YSBzb2xvIHVuYSBkZXNjYXJnYSBwb3Igb3JkZW5hZG9yLCBpbmRlcGVuZGllbnRlbWVudGUgZGVsIG7Dum1lcm8gZGUgdmVjZXMgcXVlIHNlIGRlc2NhcmdlIHVuYSBsaWJyZXLDrWEuIFJlcGl0ZSBsb3MgY8OhbGN1bG9zIGNvbnNpZGVyYW5kbyBlc3RhIHZhcmlhYmxlIHkgYWxtYWNlbmEgZWwgcmVzdWx0YWRvIGVuIGB0b3BfdW5pcXVlX3NvcnRlZGAuDQoNCmBgYHtyfQ0KDQoNCg0KDQoNCg0KDQoNCmBgYA0KMjUuIENvbXBsZXRhIGxvIHF1ZSBzZSBpbmRpY2EgIHVzYW5kbyBgcGlwZXNgLiBTZWxlY2Npb25hIGxhcyBjb2x1bW5hcyBgaXBfaWQsIGNvdW50cnksIHBhY2thZ2UsIHNpemVgIGRlIGBjcmFuYCB5IG11ZXN0cmEgZWwgcmVzdWx0YWRvIHBvciBwYW50YWxsYSB1c2FuZG8gYHByaW50YC4gTm8gZXMgbmVjZXNhcmlvIGFsbWFjZW5hci4NCg0KYGBge3J9DQoNCg0KDQpgYGANCg0KMjYuIEHDsWFkZSB1bmEgY29sdW1uYSBsbGFtYWRhIGBzaXplX21iYCBxdWUgY29udGVuZ2EgZWwgdGFtYcOxbyBkZSBjYWRhIGRlc2NhcmdhIGVuIG1lZ2FieXRlcy4NCg0KYGBge3J9DQoNCg0KDQpgYGANCg0KMjcuIFNlbGVjY2lvbmEgYXF1ZWxsYXMgZGVzY2FyZ2FzIGN1eW8gdGFtYcOxbyBlcyBtZW5vciBvIGlndWFsIHF1ZSAwLjUgTWJ5dGVzDQpgYGB7cn0NCg0KDQoNCmBgYA0KMjguIFJlcGl0ZSBlbCBhcGFydGFkbyBhbnRlcmlvciBvcmRlbmFuZG8gbG9zIHJlc3VsdGFkb3MsIGVuIG9yZGVuIGRlc2NlbmRlbnRlIHNlZ8O6biBlbCB0YW1hw7FvIGVuIE1lZ2FieXRlcyB5IG11w6lzdHJhbG8gcG9yIHBhbnRhbGxhLg0KYGBge3J9DQoNCg0KDQoNCmBgYA0KKipFWFRSQSoqLiBTZSBkZXNlYSBtb3N0YXIgZ3LDoWZpY2FtZW50ZSBhcXVlbGxvcyBwYcOtc2VzIHF1ZSBzdXBvbmVuIGVsIG1heW9yIHRyw6FmaWNvIGRlIGluZm9ybWFjacOzbiwgb2Nhc2lvbmFkbyBwb3IgZWwgdG90YWwgZGUgZGVzY2FyZ2FzIGFjdW11bGFkYXMuIFBhcmEgZmFjaWxpdGFyIGxhIHZpc3VhbGl6YWNpw7NuIGPDqW50cmF0ZSBlbiBhcXVlbGxvcyBxdWUgZXN0w6FuIHBvciBlbmNpbWEgZGVsIDkwICUgZGVsIHRvcCBkZSBkZXNjYXJnYXMuIEVsIGdyw6FmaWNvIGRlYmUgbW9zdHJhciBsb3MgdmFsb3JlcyBkZSBmb3JtYSBvcmRlbmFkYSBkZSBtYXlvciBhIG1lbm9yLiBFbiBlbCBlamUgWCBkZWJlIGFwYXJlY2VyIGVsIGPDs2RpZ28gZGUgcGHDrXMgeSBlbiBlbCBlamUgWSBlbCB0csOhZmljbyBnZW5lcmFkby4gSW50ZW50YSBoYWNlcmxvIHVzYW5kbyB1bmEgdHViZXLDrWEgc2luIGVtcGxlYXIgbmluZ3VuYSB2YXJpYWJsZSBhdXhpbGlhci4NCmBjcmFuJT4lLi4uK2dlb21fcG9pbnQoKWANCg0KYGBge3J9DQoNCg0KDQoNCmBgYA0KKipFWFRSQSoqIFRhbCBjb21vIHNlIGluZGljYSBlbiBlbCBlamVyY2ljaW8sIGVsIHNpZ25pZmljYWRvIGRlIGxvcyBjw7NkaWdvcyB1c2Fkb3MgcGFyYSBsYSBjb2RpZmljYWNpw7NuIGRlIGxvcyBwYWlzZSBzZSBwdWVkZW4gb2J0ZW5lciBkZSBodHRwczovL2Rldi5tYXhtaW5kLmNvbS9nZW9pcC9nZW9pcDIvZ2VvbGl0ZTIvLiBEZXNjYXJnYSBlbCBmaWNoZXJvICoqR2VvTGl0ZTIgQ291bnRyeSoqIGVuIGZvcm1hdG8gQ1NWLiBFc3RlIGZpY2hlcm8gY29udGllbmUgaW5mb3JtYWNpw7NuIGVuIHZhcmlvcyBpZGlvbWFzLiBVc2EgYEdlb0xpdGUyLUNvdW50cnktTG9jYXRpb25zLWVzLmNzdmAgcXVlIHRpZW5lIGxhIGluZm9ybWFjacOzbiBlbiBlc3Bhw7FvbC4gRGVzZWFtb3MgcG9kZXIgZGV0ZXJtaW5hciBsYXMgZGVzY2FyZ2FzIHBvciBjb250aW5lbnRlcyBwb3IgbG8gcXVlIG5lY2VzaXRhbW9zIGHDsWFkaXIgZXN0YSBpbmZvcm1hY2nDs24gYSBsb3MgZGF0b3MgZGUgbGFzIGRlc2NhcmdhcyBhbG1hY2VuYWRvcyBlbiBgY3JhbmAuIFByb2NlZGUgZGUgbGEgc2lndWllbnRlIGZvcm1hOg0KDQogKyBEZXNjYXJnYSBlbCBmaWNoZXJvIGRlIGPDs2RpZ29zIGRlIHBhaXNlcyBgR2VvTGl0ZTItQ291bnRyeS1Mb2NhdGlvbnMtZXMuY3N2YCBlIGltcMOzcnRhbG8gZW4gZWwgZGF0YWZyYW1lIGBjb2RlX3BhaXNlc2AuICoqT0pPIGNvbiBsYSBjb2RpZmljYWNpw7NuKioNCiArIENhcmdhIHkgYWxtYWNlbmEgbGEgaW5mb3JtYWNpw7NuIGRlbCBmaWNoZXJvIGRlIGxvcyBwYWlzZXMuDQogKyBFbGltaW5hIHRvZG9zIGxvcyByZWdpc3Ryb3MgZGUgYGNyYW5gIHF1ZSBubyBpbmNsdXlhbiBlbCBjw7NkaWdvIGRlbCBwYcOtcy4NCiArIEVsaW1pbmEgbG9zIHJlZ2lzdHJvcyBkZSBgY29kZV9wYWlzZXNgIHF1ZSBubyBpbmNsdXllbiBlbCBjw7NkaWdvIGRlbCBwYcOtcy4NCiArIENvbWJpbmEgbG9zIGRvcyBkYXRhIGZyYW1lcyBwYXJhIGHDsWFkaXIgYSBgY3JhbmAgZWwgY8OzZGlnbyBkZWwgcGHDrXMgeSBlbCBjb250aW5lbnRlLCBlbCByZXN0byBkZSB2YXJpYWJsZXMgbm8gc29uIG5lY2VzYXJpYXMuDQogKyBSZXByZXNlbnRhIHVuIGRpYWdyYW1hIGRlIGJhcnJhcyBjb24gZWwgZmx1am8gZGUgZGF0b3MsIHBvciBjb250aW5lbnRlLCBvcmRlbmFkb3MgZGUgbWF5b3IgYSBtZW5vci4gRXhwcmVzYSBlbCBmbHVqbyBkZSBkYXRvcyBlbiBUZXJhYnl0ZXMgKDFUYj0yXjQwYnl0ZXMpDQogDQpgYGB7cn0NCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KYGBgDQoNCiMgdGlkeXIuDQpDYXJnYSBlbCBmaWNoZXJvIGRlIGRhdG9zIGBkYXRvc3RpZHlyLlJkYXRhYCBxdWUgY29udGllbmUgdmFyaW9zIGNvbmp1bnRvcyBkZSBkYXRvcyBxdWUgdmFtb3MgYSB1c2FyIGVuIGxvcyBzaWd1aWVudGVzIGVqZXJjaWNpb3MuDQoNCjI5LiBPYnNlcnZhIGVsIGRhdGEgZnJhbWUgYHN0dWRlbnRzYCA6DQogIGdyYWRlIG1hbGUgZmVtYWxlDQoxICAgICBBICAgIDEgICAgICA1DQoyICAgICBCICAgIDUgICAgICAwDQozICAgICBDICAgIDUgICAgICAyDQo0ICAgICBEICAgIDUgICAgICA1DQo1ICAgICBFICAgIDcgICAgICA0DQoNCkxhIGNvbHVtbmEgYGdyYWRlYCBlcyBsYSBjYWxpZmljYWNpw7NuIHkgbGFzIGRvcyBzaWd1aWVudGVzIGVsIG7Dum1lcm8gZGUgZXN0dWRpYW50ZXMgaG9tYnJlIHkgbXVqZXIgcXVlIGhhbiByZWNpYmlkbyBkaWNoYSBjYWxpZmljYWNpw7NuLg0Kwr8gQ3XDoWxlcyBzb24gbGFzIHZhcmlhYmxlcyA/LiBUcmFuc2bDs3JtYWxvIGVuIHVuIGNvbmp1bnRvIGB0aWR5YCBjb24gbGFzIHZhcmlhYmxlcywgYGdyYWRlLCBzZXggeSBjb3VudGANCmBgYHtyfQ0KDQoNCg0KYGBgDQozMC4gQ29uc2lkZXJhIGVsIGNvbmp1bnRvIMK0c3R1ZGVudHMywrQsIHF1ZSBlcyBzaW1pbGFyIGFsIHByaW1lcm8gcGVybyBxdWUgc2UgaGFuIHJlY29naWRvIGRhdG9zIGNvcnJlc3BvbmRpZW50ZXMgYSBkb3MgZ3J1cG9zLCAoYG1hbGVfMSwgZmVtYWxlXzFgLCBldGMpLiBUcmFuc2bDs3JtYWxvIGVuIHVuIGNvbmp1bnRvIGB0aWR5YCBhw7FhZGllbmRvIHVuYSBjb2x1bW5hIGFkaWNpb25hbCBjb3JyZXNwb25kaWVudGUgYWwgZ3J1cG8gKGxsYW1hIGEgbGEgbnVldmEgdmFyaWFibGUgYGNsYXNzYCkNCmBgYHtyfQ0KDQoNCg0KDQpgYGANCjMxLiBDb25zaWRlcmEgZWwgY29uanVudG8gYHN0dWRlbnRzM2AgeSBvYnNlcnZhIHBvciBxdcOpIG5vIGVzIHVuIGB0aWR5IGRhdGFgIC4gTGFzIHZhcmlhYmxlcyBmaW5hbGVzIGRlbCBjb25qdW50byBkZWJlcsOtYW4gc2VyOiBgbmFtZSwgY2xhc3MsIG1pZHRlcm0sIGZpbmFsYC4gRGFkbyBxdWUgdW4gYWx1bW5vIHNvbG8gcHVlZGUgZXN0YXIgZW4gdW5hIGRlIGxhcyBjbGFzZXMgYXBhcmVjZW4gbXVjaG9zIGBOQWAsIGFsIHJlb3JnYW5pemFyLCB1c2EgYG5hLnJtPVRSVUVgIGVuIGxhcyBvcGNpb25lcyBkZSBgZ2F0aGVyYA0KYGBge3J9DQoNCg0KDQoNCmBgYA0KMzIuIENvbnNpZGVyYSBxdWUgbGEgY29sdW1uYSBgY2xhc3NgIHF1ZXJlbW9zIGVzcGVjaWZpY2FybGEgY29uIHZhbG9yZXMsIDEsMiwzLDQgeSA1IGVuIGx1Z2FyIGRlIGNsYXNzMSwgLi4uLCBjbGFzczUuIEVuIGxpYnJlcsOtYSBkZSBpbXBvcnRhY2nDs24gZGUgZGF0b3MgKGByZWFkcmApIHNlIHByb3BvcmNpb25hbiBmdW5jaW9uZXMgcXVlIHBlcm1pdGUgaWRlbnRpZmljYXIgeSBleHRyYWVyIGRpdmVyc29zIHRpcG9zIGRlIGRhdG9zLiBTb24gbGFzIGZ1bmNpb25lcyBgcGFyc2VfWFhYWGAuIE9ic2VydmEgZWwgZWZlY3RvIGRlIGFwbGljYXIgYHBhcnNlX251bWJlcignY2xhc3M1JylgIHkgdXRpbGl6YSBlbCByZXN1bHRhZG8gcGFyYSByZWFsaXphciBsYSB0YXJlYSBzb2xpY2l0YWRhLg0KDQpgYGB7cn0NCg0KDQoNCg0KDQoNCmBgYA0KMzMuIE9ic2VydmEgZWwgY29uanVudG8gYHN0dWRlbnRzNGAgZG9uZGUgc2UgcHJlc2VudGEgZWwgcHJvYmxlbWEgZGUgdGVuZXIgdmFyaWFzIG9ic2VydmFjaW9uZXMgZW4gbGEgbWlzbWEgdGFibGEuIFNlcGFyYSBlbCBjb25qdW50byBlbiBkb3MgdGFibGFzLCB1bmEgcXVlIGNvbnRlbmdhIGluZm9ybWFjacOzbiByZWxhdGl2YSBhIGxvcyBlc3R1ZGlhbnRlcywgcXVlIGxsYW1hcsOhcyBgc3R1ZGVudF9pbmZvYCwgcXVlIG5vIGRlYmUgY29udGVuZXIgcmVwZXRpY2lvbmVzLCB5IG90cmEgY29uIGxhcyBjYWxpZmljYWNpb25lcyB5IGxhIGNsYXZlIHByaW1hcmlhIHF1ZSBlbmxhemEgYW1iYXMgdGFibGFzKGBpZGApLCBxdWUgbGxhbWFyw6FzIGBncmFkZWJvb2tgLg0KYGBge3J9DQoNCg0KDQoNCg0KDQoNCg0KDQpgYGANCjM0LiBDb25zaWRlcmVtb3MgZWwgY2FzbyBkZSB1bmEgb2JzZXJ2YWNpw7NuIGFsbWFjZW5hZGEgZW4gdmFyaWFzIHRhYmxhcy4gT2JzZXJ2YSBsb3MgZGF0b3MgYWxtYWNlbmFkb3MgZW4gYGZhaWxlZGAgeSBgcGFzc2VkYCBjb24gbGEgaW5mb3JtYWNpw7NuIGRlIGxvcyBhbHVtbm9zIHF1ZSBzdXNwZWRlbiAoY2FsaWZpY2FjaW9uZXMgQyxELEUsRikgeSBsb3MgcXVlIGFwcnVlYmFuIChjYWxpZmljYWNpb25lcyBBLCBCKS4gRGViZXMgdW5pciBhbWJvcyBjb25qdW50b3MgZW4gdW5vIHF1ZSBjb250ZW5nYSB1bmEgY29sdW1uYSBhZGljaW9uYWwgbGxhbWFkYSBgc3RhdHVzYCBxdWUgaW5kaWNhcsOhIGBwYXNzZWRgIG8gYGZhaWxlZGAgc2Vnw7puIGxhIHNpdHVhY2nDs24gZGUgY2FkYSBhbHVtbm8sIHB1ZWRlcyB1c2FyIGxhIGZ1bmNpw7NuIGBiaW5kX3Jvd3NgLg0KYGBge3J9DQoNCg0KDQpgYGANCiMgUG9uZ2Ftb3MgdG9kbyBlbiBjb23Dum4uDQoNCjM1LiBFbCBTQVQgZXMgdW4gZXhhbWVuIHBvcHVsYXIgZGUgcHJlcGFyYWNpw7NuIHBhcmEgbGEgdW5pdmVyc2lkYWQgZW4gbG9zIEVzdGFkb3MgVW5pZG9zIHF1ZSBjb25zdGEgZGUgdHJlcyBwYXJ0ZXM6IGxlY3R1cmEgY3LDrXRpY2EsIG1hdGVtw6F0aWNhcyB5IGVzY3JpdHVyYS4gTG9zIGVzdHVkaWFudGVzIHB1ZWRlbiBvYnRlbmVyIGhhc3RhIDgwMCBwdW50b3MgZW4gY2FkYSBwYXJ0ZS4gRXN0ZSBjb25qdW50byBkZSBkYXRvcyBwcmVzZW50YSBlbCBuw7ptZXJvIHRvdGFsIGRlIGVzdHVkaWFudGVzLCBwYXJhIGNhZGEgY29tYmluYWNpw7NuIGRlIHBhcnRlIHkgc2V4byBkZWwgZXN0dWRpYW50ZS4gDQpDb25zaWRlcmEgbG9zIGRhdG9zIGFsbWFjZW5hZG8gZW4gYHNhdGAgeSBoYXogbGFzIHRyYW5zZm9ybWFjaW9uZXMgYWRlY3VhZGFzIHBhcmEgcXVlIGxhcyBjb2x1bW5hcyBkZWwgY29uanVudG8gYHRpZHlgIHNlYW4gYHNjb3JlX3JhbmdlLCBwYXJ0LCBzZXgsIGNvdW50YCBlbiBlc2Ugb3JkZW4uIFRlbiBlbiBjdWVudGEgcXVlIGxvcyBkYXRvcyB0b3RhbGVzIG5vIGVzIG5lY2VzYXJpbyBhbG1hY2VuYXJsb3MgeWEgcXVlIHNlIHB1ZWRlbiBnZW5lcmFyIGEgcG9zdGVyaW9yaS4NCg0KYGBge3J9DQoNCg0KDQoNCg0KDQpgYGANCg0KMzYuIFV0aWxpemEgYWdydXBhbWllbnRvcywgc2Vnw7puIGxhcyB2YXJpYWJsZXMgYHBhcnQsIHNleGAgeSBnZW5lcmEgZG9zIG51ZXZhcyBjb2x1bW5hcyBxdWUgY29udGVuZ2FuIGVsIG7Dum1lcm8gdG90YWwgZGUgZXN0dWRpYW50ZXMgZW4gZGljaG8gZ3J1cG8sIHkgbGEgcHJvcG9yY2nDs24gZGVudHJvIGRlbCBncnVwby4gQWxtYWPDqW5hbGFzIGVuIGxhcyB2YXJpYWJsZXMgYHRvdGFsIHkgcHJvcGAgcmVzcGVjdGl2YW1lbnRlLg0KYGBge3J9DQoNCg0KDQoNCg0KDQoNCg0KDQpgYGANCjM3LiBSZXByZXNlbnRhIGdyw6FmaWNhbWVudGUgZWwgY29uanVudG8gb2J0ZW5pZG8gcGFyYSBtb3N0cmFyIGxhcyBkaWZlcmVuY2lhcyBlbnRyZSBob21icmVzIHkgbXVqZXJlcywgZW4gbGFzIGRpZmVyZW50ZXMgcHJ1ZWJhcyBwYXJhIGNhZGEgdW5hIGRlIGxhcyBmcmFuamFzIGRlIGNhbGlmaWNhY2nDs24uDQpgYGB7cn0NCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCmBgYA0KDQoNCiMgTWFuZWpvIGRlIGZlY2hhcyBjb24gbHVicmlkYXRlLg0KDQozOC4gTGEgZnVuY2nDs24gYHRvZGF5YCBkZXZ1ZWx2ZSBsYSBmZWNoYSBhY3R1YWwuIEFsbWFjw6luYSBsYSBmZWNoYSBhY3R1YWwgZW4gYHRoaXNfZGF5YCwgeSBtdcOpc3RyYWxhIHBvciBwYW50YWxsYS4gVXNhIGxhcyBmdW5jaW9uZXMgYHllYXIsIG1vbnRoIHkgZGF5YCBwYXJhIGV4dHJhZXIgY2FkYSB1bmEgZGUgbGFzIHBhcnRlcy4gYHdkYXlgIHBhcmEgb2J0ZW5lciBlbCBkw61hIGRlIGxhIHNlbWFuYSAoKQ0KYGBge3J9DQoNCg0KDQoNCg0KDQoNCmBgYA0KMzkuIEd1YXJkYSBlbCBpbnN0YW50ZSBhY3R1YWwgZW4gdW5hIHZhcmlhYmxlIGxsYW1hZGEgYHRoaXNfbW9tZW50YCBjb24gbGEgZnVuY2nDs24gYG5vd2ANCnkgbXXDqXN0cmFsbyBwb3IgcGFudGFsbGEuIFVzYSBsYXMgZnVuY2nDs25lcyBgaG91ciwgbWludXRlLCBzZWNvbmRgIHBhcmEgZXh0cmFlciBsYSBpbmZvcm1hY2nDs24gZGUgY2FkYSBlbGVtZW50by4NCmBgYHtyfQ0KDQoNCg0KDQoNCmBgYA0KNDAuIExhIGxpYnJlcsOtYSBgbHVicmlkYXRlYCBkaXNwb25lIGRlIG3Dumx0aXBsZXMgZnVuY2lvbmVzIHBhcmEgaW1wb3J0YXIgZW4gZm9ybWF0byBmZWNoYSBkYXRvcyBjb24gdW4gZ3JhbiB2YXJpZWRhZCBkZSBmb3JtYXRvcyBkZSBlbnRyYWRhIGB5bWQoKSwgZG15KCksIGhtcygpLCB5bWRfaG1zKClgIGRvbmRlIGNhZGEgbGV0cmEgaW5kaWNhIF95ZWFycyAoeSksbW9udGhzIChtKSwgZGF5cyAoZCksIGhvdXJzIChoKSwgbWludXRlcyAobSksIHkgc2Vjb25kcyAocylfLiBQcnVlYmEgbXlfZGF0ZSA8LSB5bWQoIjIwMTktMDItMTQiKSwgeSBkZXRlcm1pbmEgZGUgcXVlIGNsYXNlIGVzIGBteV9kYXRlYC4gTGFzIGZ1bmNpb25lcyBzb24gbXV5IHJvYnVzdGFzIHkgcHVlZGVuIGludGVwcmV0YXIgY29ycmVjdGFtZW50ZSBkaXZlcnNvcyBmb3JtYXRvLiBQcnVlYmEgYHltZCgpYCBwYXJhIGltcG9ydGFyIGxhIGZlY2hhICBfXyIxOTg5IE1heSAxNyJfXy4gwr9Dw7NtbyBpbXBvcnRhcsOtYXMgX18iTWFyY2ggMTIsIDE5NzUiX18/DQpgYGB7cn0NCg0KDQoNCmBgYA0KNDEuIEltcG9ydGEgZWwgdmFsb3IgbnVtw6lyaWNvIGAyNTA4MTk4NWAsIHNhYmllbmRvIHF1ZSBlbCBmb3JtYXRvIGVzIGRpYSwgbWVzLCBhw7FvLCB5IHBvc3Rlcmlvcm1lbnRlIGAxOTIwMTJgIGRvbmRlIHNlIGhhIGFsbWFjZW5hZG8gYcOxbywgbWVzIHkgZMOtYS4gTW9kaWZpY2EgZWwgY2FtcG8gZmVjaGEgZW4gZWwgc2VndW5kbyBjYXNvIGHDsWFkaWVuZG8gYWxnw7puIGNhcsOhY3RlciBzZXBhcmFkb3IgcGFyYSBlbGltaW5hciBsYSBhbWJpZ8O8ZWRhZC4NCmBgYHtyfQ0KZmVjaGE8LScxOTIwMDEwMicNCnltZChmZWNoYSkNCg0KDQoNCg0KYGBgDQo0Mi4gQWxtYWNlbmEgbGEgY2FkZW5hIGAyMDE0LTA4LTIzIDE3OjIzOjAyYCBlbiBsYSB2YXJpYWJsZSBgZHQxYCAgZSBpbXDDs3J0YWxhIHBhcmEgcXVlIGluY2x1eWEgbGEgZmVjaGEgeSBsYSBob3JhLHBvc3Rlcmlvcm1lbnRlIGltcG9ydGEgbGEgY2FkZW5hIGAiMDM6MjI6MTQiYCAoaGg6bW06c3MpDQpgYGB7cn0NCg0KDQoNCg0KYGBgDQo0My4gSW1wb3J0YSBlbCB2ZWN0b3IgZGUgZmVjaGFzIGFsbWFjZW5hZG8gZWwgYGR0MmANCmBgYHtyfQ0KDQpgYGANCjQ0LiBQb2RlbW9zIHJlYXNpbmdhciBudWV2b3MgdmFsb3JlcyBhIHVuYSB2YXJpYWJsZSBmZWNoYS9ob3JhIGNvbiBsYSBmdW5jacOzbiBgdXBkYXRlYA0KLCBwb3IgZWplbXBsbzogYHVwZGF0ZSh0aGlzX21vbWVudCwgaG91cnMgPSA4LCBtaW51dGVzID0gMzQsIHNlY29uZHMgPSA1NSlgLiBBY3R1YWxpemEgbGEgdmFyaWFibGUgYHRoaXNfbW9tZW50YCBjcmVhZGEgYW50ZXJpb3JtZW50ZSBwYXJhIHF1ZSBjb250ZW5nYSBsYSBob3JhIGFjdHVhbC4NCmBgYHtyfQ0KDQpgYGANCjQ1LiBfX1pvbmFzIGhvcmFyaWFzX18uIENvbnNpZGVyYSBxdWUgZXN0w6FzIGVuIE51ZXZhIFlvcmssIHF1ZSBwbGFuZWFzIHZpc2l0YXIgYSB1biBhbWlnbyBlbiBIb25nIEtvbmcgeSBxdWUgaGFzIHBlcmRpZG8gdHUgaXRpbmVyYXJpbywgcGVybyBzYWJlcyBxdWUgdHUgdnVlbG8gc2FsZSBkZSBOdWV2YSBZb3JrIGEgbGFzIDE3OjM0ICgxNzozNCBob3JhcykgcGFzYWRvIG1hw7FhbmEuIFRhbWJpw6luIHNhYmVzIHF1ZSBlbCB2dWVsbyBlc3TDoSBwcm9ncmFtYWRvIHBhcmEgbGxlZ2FyIGEgSG9uZyBLb25nIGV4YWN0YW1lbnRlIDE1IGhvcmFzIHkgNTAgbWludXRvcyBkZXNwdcOpcyBkZSBsYSBzYWxpZGEuIFZhbW9zIGEgcmVjb25zdHJ1aXIgZWwgaXRpbmVyYXJpbzoNCiAgKyBHZW5lcmEgbGEgZmVjaGEgYWN0dWFsLCBjb24gYG5vd2AgZSBpbmRpY2EgbGEgem9uYSBob3JhcmlhIGRlIE51ZXZhIFlvcmsoQW1lcmljYS9OZXdfWW9yaykgKGxhIGZ1bmNpw7NuIGBPbHNvbk5hbWVzKClgIGRldnVlbHZlIHRvZGFzIGxhcyB6b25hcyBob3JhcmlhcykgeSBhbG1hY8OpbmFsYSBlbiBsYSB2YXJpYWJsZSBgbnljYC4NCiAgKyBBw7FhZGUgZG9zIGTDrWFzIHkgYWxtYWPDqW5hbG8gZW4gbGEgdmFyaWFibGUgYHNhbGlkYWAuIFB1ZWRlcyB1c2FyIGxhIGZ1bmNpw7NuIGBkYXlzYCB5IHN1bWFyIGRpcmVjdGFtZW50ZS4gQ29tcHJ1ZWJhIHF1ZSBsYSBzYWxpZGEgdGllbmUgZWwgdmFsb3IgY29ycmVjdG8uDQogICsgQWN0dWFsaXphIGxhIHZhcmlhYmxlIHNhbGlkYSBjb24gbGEgaG9yYSByZWFsIGRlIHNhbGlkYS4NCiAgKyBBdmVyaWd1YSBhIHF1w6kgaG9yYSBsbGVnYXLDoXMgYSBIb25nIEtvbmcsIGNvbiByZWZlcmVuY2lhIGhvcmFyaWEgZGUgTlkuDQogICsgQXZlcmlndWEgYSBxdcOpIGhvcmEgbGxlZ2Fyw6FzIGEgSG9uZyBLb25nLCBjb24gcmVmZXJlbmNpYSBob3JhcmlhIGxvY2FsLiBVc2EgbGEgZnVuY2nDs24gYHdpdGhfdHpgIHBhcmEgcmVhbGl6YXIgZXN0YSB0YXJlYS4NCiAgKyBMYSDDumx0aW1hIHZleiBxdWUgdmlzdGUgYSB0dSBhbWlnbyBmdWUgZW4gU2luZ2FwdXIgZWwgMTcgZGUgSnVuaW8gZGUgMjAwOC4gR2VuZXJhIGRpY2hhIGZlY2hhIGVuIHpvbmEgaG9yYXJpYSBkZSBTaW5nYXB1ciB5IHV0aWxpemEgbGEgZnVuY2lvbiBgaW50ZXJ2YWxgIHBhcmEgY3JlYXIgZWwgdHJhbW8gdGVtcG9yYWwgZW50cmUgYW1iYXMgZmVjaGFzIHkgYXBsaWNhIGxhIGZ1bmNpw7NuIGBhcy5wZXJpb2RgIHNvYnJlIGVsIGludGVydmFsbyByZXN1bHRhbnRlIHBhcmEgY2FsY3VsYXIgZXjDoWN0YW1lbnRlIGN1w6FudG8gdGllbXBvIGhhY2UgZGVzZGUgcXVlIG9zIHZpc3RlaXMuDQogIEN1YW5kbyBjb25zaWRlcmFtb3Mgem9uYXMgaG9yYXJpYXMsIHBlcmlkb3MgcXVlIGluY2x1eWVuIGHDsW9zIGJpc2llc3RvcywgZXRjLiBFbCBtYW5lam8gZGUgZmVjaGEgcmVxdWllcmUgZGUgbGlicmVyw61hcyBjb21vIF9fbHVicmlkYXRlX18gcGFyYSBmYWNpbGl0YXIgZXN0YXMgdGFyZWFzLg0KICANCmBgYHtyfQ0KDQoNCg0KDQoNCg0KDQoNCg0KYGBgDQogIA0KICANCg0K